#pragma once
#include <osg/Array>
#include <osg/Texture2D>
#include <utility>
#include "core/CPair.h"
#include "core/CString.h"
#include "core/CVector.h"
#include "OUtil.h"
#include "Enum.h"

#define Field(K,V) MakePair(K,V)

/**@brief Composition: 'has-a' -> Entity has a Uniform */
class OUniform {
public:
	OUniform() = default;

	OUniform(osg::StateSet* stateSet) { m_stateSet = stateSet; }

	bool isValid() { return m_stateSet != nullptr; }

	template<typename T>
	void add(GLSLVarName name, const T& value) { add(Enum_toStr(name), value); }

	template<typename T>
	void add(const std::string& name, const T& value, osg::Texture2D* texture = nullptr) {
		if (texture) {// value is GLuint(Texture Unit)
			if constexpr (std::is_same_v<T, int> || std::is_same_v<T, unsigned int>)
				m_stateSet->setTextureAttributeAndModes(value, texture);
		}
		if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double>)
			addUniform(name, (float)value);
		else if constexpr (std::is_same_v<T, P>)
			addUniform(name, osg::Vec2(value.x, value.z));
		else if constexpr (std::is_same_v<T, P2D>)
			addUniform(name, osg::Vec2(value.x, value.y));
		else if constexpr (std::is_same_v<T, P3D>)
			addUniform(name, osg::Vec3(value.x, value.y, value.z));
		else if constexpr (std::is_same_v<T, CColor>)
			addUniform(name, osg::Vec3(value.redF(), value.greenF(), value.blueF()));
		else if constexpr (std::is_same_v<T, Mat>)
			addUniform(name, OUtil::toMatrix(value));
		else
			addUniform(name, value);
	}

	/**@example add("zStart", 0.1f, "zEnd", 96, "edgeW", 33);*/
	void add(const std::string&) {}// a must: stop condition
	template<typename T, typename... Args>
	void add(const std::string& name, T value, const std::string& nextName, Args... args) {
		add(name, value);
		if (sizeof...(args) > 0)
			add(nextName, args...);
	}

	/**@example addStruct("material", Field("baseColor", myBaseColor), Field(k2,v2), ...);*/
	void addStruct(const std::string&) {}// a must: stop condition
	template<typename T, typename... Args>
	void addStruct(const std::string& structName, const Pair<const char*, T>& field, Args... args) {
		add(structName + "." + field.key, field.value);
		if (sizeof...(args) > 0)
			addStruct(structName, args...);
	}

	void removeStruct(const std::string& structName, const std::initializer_list<std::string>& fields) {// C++17 use `std::string-view` to avoid unnecessary copying of `std::string`
		CString uname = "struct.%1";
		for (const auto& field : fields)
			remove(uname.arg(field));
	}

	template<typename T>
	void addArray(const std::string& name, const CVector<T>& values) {
		auto uniform = m_stateSet->getUniform(name);
		if (!uniform) {
			uniform = new osg::Uniform;
			uniform->setName(name);
			m_stateSet->addUniform(uniform);
		}
		if constexpr (std::is_same_v<T, int>)
			uniform->setType(osg::Uniform::INT);
		else if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double>)
			uniform->setType(osg::Uniform::FLOAT);

		uniform->setNumElements(values.size());
		for (int i = 0; i < values.size(); i++)
			uniform->setElement(i, values[i]);
	}

	void remove(const std::string& name) {
		m_stateSet->removeUniform(name);
	}

private:
	template<typename T>
	void addUniform(const std::string& name, const T& osgValue) {
		auto uniform = m_stateSet->getUniform(name);
		if (uniform)
			uniform->set(osgValue);
		else
			m_stateSet->addUniform(new osg::Uniform(name.c_str(), osgValue));
	}

	osg::StateSet* m_stateSet = nullptr;
};